<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Debounce</title>
  </head>
  <body>
    <input type="text" id="input" />
    <script type="text/javascript">
      input.addEventListener('input', debounce(inputTest, 500), false);

      function inputTest() {
        console.log(this.value);
      }

      /*
       * 英文翻译：
       *  debounce 去抖动;防反跳;防抖动;弹跳;抖动消除
       */
      function debounce(fn, delay) {
        // 在debounce外层函数作用域中声明变量 t 保存定时器
        // 利用闭包将外层函数的AO对象保存在内层函数的作用域链上
        // 闭包相关知识见MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures
        // 大家有兴趣可以console.dir("得到的函数")观察[[Scopes]]
        var t = null;
        // 返回内部函数给外界
        return function () {
          // 保存内部函数的this指向和实参列表
          /**
           * 解释一下：
           *   一般的防抖以及节流函数会用在事件绑定上，而真正绑定的函数就是这个内部函数
           *   而我们知道, 给元素绑定事件处理函数后, 事件处理函数的this指向的是被绑定的元素
           *   且会有实参event（事件对象）的传入, 因此我们需要保存对应的this和实参
           */
          var _self = this,
            args = arguments;
          // 若外部函数作用域的t被赋值了（以下直接以"t"叙述）
          // 则需要清除上一次设置的定时器
          // ps: setTimeout会返回一个当前定时器的唯一id
          //  可以通过该id取消设定的定时器
          if (t) {
            clearTimeout(t);
          }

          // 如果使用箭头函数, 可以不适用_self来保存this
          t = setTimeout(function () {
            // 利用this的显示绑定, 将内部函数的this绑定给目标函数fn
            // 以及将实参对象以(类)数组的方式传入到fn中
            // 同时调用fn -> 达成防抖的效果
            fn.apply(_self, args);
          }, delay);
        };
      }
    </script>
  </body>
</html>
